# Python3 错误和异常
'''
Python 有两种错误很容易辨认：语法错误和异常。
Python assert（断言）用于判断一个表达式，在表达式条件为 false 的时候触发异常。
'''

## 语法错误
'''
Python 的语法错误或者称之为解析错误
'''

## 异常
'''
即便 Python 程序的语法是正确的，在运行它的时候，也有可能发生错误。运行期检测到的错误被称为异常。
大多数的异常都不会被程序处理，都以错误信息的形式展现出来。
'''
#10 * (1/0) # 0 不能作为除数，触发异常 ZeroDivisionError: division by zero
#4 + spam*3 # spam 未定义，触发异常 NameError: name 'spam' is not defined
#'2' + 2 # int 不能与 str 相加，触发异常 TypeError: can only concatenate str (not "int") to str

## 异常处理 try/except
'''
try 语句按照如下方式工作；
1.首先，执行 try 子句（在关键字 try 和关键字 except 之间的语句）。
2.如果没有异常发生，忽略 except 子句，try 子句执行后结束。
3.如果在执行 try 子句的过程中发生了异常，那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符，那么对应的 except 子句将被执行。
4.如果一个异常没有与任何的 excep 匹配，那么这个异常将会传递给上层的 try 中。
'''
while True:
    try:
        #x = int(input("请输入一个整数: "))
        break
    # 一个 try 语句可能包含多个except子句，分别来处理不同的特定的异常。最多只有一个分支会被执行。
    except ValueError:
        print("您输入的不是数字，请再次尝试输入！")
### 一个except子句可以同时处理多个异常，这些异常将被放在一个括号里成为一个元组
try:
    f = open('noexist.txt')
except (RuntimeError, FileNotFoundError, OSError) as err:
    print("error: {0}".format(err))
    pass
### 最后一个except子句可以忽略异常的名称，它将被当作通配符使用。可以使用这种方法打印一个错误信息，然后再次把异常抛出。
import sys
try:
    f = open('noexist.txt')
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("无法将数据转换为整数.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    # 抛出异常
    raise
### try/except...else
'''
try/except 语句还有一个可选的 else 子句，如果使用这个子句，那么必须放在所有的 except 子句之后。
else 子句将在 try 子句没有发生任何异常的时候执行。
'''
for arg in sys.argv[0:]:
    try:
        f = open(arg, mode='r', encoding="UTF-8")
    except IOError:
        print('无法打开：', arg)
    else:
        print(arg, '正常打开，共有 ', len(f.readlines()), ' 行')
        f.close()
### 还能处理子句中调用的函数（甚至间接调用的函数）里抛出的异常
def this_fails():
    x = 1/0

try:
    this_fails()
except ZeroDivisionError as err:
    print('处理【函数调用】运行时错误: ', err)
### try-finally 语句（无论是否发生异常都将执行最后的代码）
try:
    1 + 1
except AssertionError as error:
    print(error)
else:
    try:
        with open('noexists.file') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('这句话，无论异常是否发生都会执行。')
### 抛出异常： raise [Exception [, args [, traceback]]]
x = 10
if x > 5:
    #raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
    pass
'''
raise 唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类（也就是 Exception 的子类）。
如果只想知道这是否抛出了一个异常，并不想去处理它，那么一个简单的 raise 语句就可以再次把它抛出。
'''
try:
    raise NameError('HiThere')
except NameError:
    print('一个异常捕获后，又抛出~')
    #raise
### 用户自定义异常
'''
可以通过创建一个新的异常类来拥有自己的异常。异常类继承自 Exception 类，可以直接继承，或者间接继承
'''
class MyError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)
try:
    raise MyError(2*2)
except MyError as e:
    print('MyError异常出现, 值是: ', e.value)
'''
当创建一个模块有可能抛出多种不同的异常时，一种通常的做法是为这个包建立一个基础异常类，
然后基于这个基础类为不同的错误情况创建不同的子类.
大多数的异常的名字都以"Error"结尾，就跟标准的异常命名一样
'''
### 预定义的清理行为，自动关闭资源（感觉很像java7+的 try-with-resource）
'''
代码执行完毕后，就算在处理过程中出问题了，文件 f 总是会关闭
'''
with open(sys.argv[0], mode='r', encoding="UTF-8") as f:
    print(len(f.readlines()))

## assert（断言）: assert expression [, arguments]
'''
Python assert（断言）用于判断一个表达式，在表达式条件为 false 的时候触发异常。
断言可以在条件不满足程序运行的情况下直接返回错误，而不必等待程序运行后出现崩溃的情况.
'''
assert 1==1    # 条件为 true 正常执行
### assert 后面也可以紧跟参数
### 判断当前系统是否为 windows，如果不满足条件则直接触发异常
import sys
assert ('win32' in sys.platform), "该代码只能在 windows 下执行"
### 等价于 手动抛出 AssertionError
#assert 1==2, "1不等于2"    # 条件为 false 触发异常
if not 1==2:
    raise AssertionError("1不等于2")